package com.fitso.model.dao;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.junit.Before;
import org.junit.Test;

import com.fitso.model.bean.measure.energy.Calorie;
import com.fitso.model.bean.measure.mass.Gram;
import com.fitso.model.bean.measure.mass.MicroGram;
import com.fitso.model.bean.measure.mass.MilliGram;
import com.fitso.model.bean.measure.mass.Ounce;
import com.fitso.model.bean.nutrition.Carbohydrates;
import com.fitso.model.bean.nutrition.Fats;
import com.fitso.model.bean.nutrition.Food;
import com.fitso.model.bean.nutrition.FoodDescription;
import com.fitso.model.bean.nutrition.Minerals;
import com.fitso.model.bean.nutrition.Protein;
import com.fitso.model.bean.nutrition.Serving;

/**
 * Abstract test that verifies behavior of any implementation of {@link FoodDao}
 * 
 * @author timothystorm
 */
public abstract class FoodDaoDB_IT extends DatabaseIntegrationTestUnit {
	protected FoodDao dao;

	protected Food twinkie, bananaTwinkie;

	public abstract FoodDao newFoodDao();

	@Before
	public void setUp() throws Exception {
		super.setUp();
		dao = newFoodDao();

		Carbohydrates carbs = new Carbohydrates();
		carbs.setTotal(new Gram(27));
		carbs.setSugar(new Gram(19));

		Fats fats = new Fats();
		fats.setTotal(new Gram(4.5));
		fats.setSaturated(new Gram(2.5));

		Minerals min = new Minerals();
		min.setCalcium(new MilliGram(20));
		min.setSodium(new MilliGram(220));

		twinkie = new Food();
		twinkie.setDescription(new FoodDescription("Cake", "Hostess", "Twinkie"));
		twinkie.setServing(new Serving(new Ounce(1.5), "1 Twinkie"));
		twinkie.setCalories(new Calorie(150));
		twinkie.setProtein(new Protein(new Gram(1)));
		twinkie.setCarbs(carbs);
		twinkie.setFats(fats);
		twinkie.setMinerals(min);
		
		carbs = new Carbohydrates();
		carbs.setTotal(new Gram(28));
		carbs.setSugar(new Gram(20));

		fats = new Fats();
		fats.setTotal(new Gram(4.6));
		fats.setSaturated(new Gram(2.6));

		min = new Minerals();
		min.setCalcium(new MilliGram(21));
		min.setSodium(new MilliGram(221));

		bananaTwinkie = new Food();
		bananaTwinkie.setDescription(new FoodDescription("Cake", "Hostess", "Banana Twinkie"));
		bananaTwinkie.setServing(new Serving(new Ounce(1.5), "1 Twinkie"));
		bananaTwinkie.setCalories(new Calorie(155));
		bananaTwinkie.setProtein(new Protein(new Gram(1)));
		bananaTwinkie.setCarbs(carbs);
		bananaTwinkie.setFats(fats);
		bananaTwinkie.setMinerals(min);
	}

	@Test
	public void test_round_tripping_multiple_food() {
		List<Food> foods = new ArrayList<Food>();
		for (int i = 0; i < 100; i++) {
			Food f = new Food();
			f.setCalories(new Calorie(RandomUtils.nextInt(500) + 1));
			f.setFats(new Fats(new Gram(30)));
			f.setCarbs(new Carbohydrates(new Gram(35)));
			f.setDescription(new FoodDescription(RandomStringUtils.randomAlphabetic(8)));
			f.setServing(new Serving(new Ounce(RandomUtils.nextInt(200)), RandomStringUtils
					.randomAlphabetic(12)));
			foods.add(f);
		}
		dao.createOrUpdate(foods);

		List<Food> foodList = dao.read();
		assertTrue(foodList.containsAll(foods));

		dao.delete(foods);

		foodList = dao.read();
		assertFalse(foodList.contains(foods));
		
		List<Food> idFoods = dao.readById(Arrays.asList(new Long[]{0L, 3L, 6L, 7L}));
		assertNotNull(idFoods);
		assertEquals(4, idFoods.size());
		assertTrue(foodList.containsAll(idFoods));
	}

	@Test
	public void test_round_tripping_single_food() {
		// assert it doesn't already exist
		Food fetchedFood = dao.readById(twinkie.getId());
		assertNull(fetchedFood);

		// save a new food
		dao.createOrUpdate(twinkie);

		// refetch
		fetchedFood = dao.readById(twinkie.getId());
		assertNotNull(fetchedFood);
		assertEquals(twinkie, fetchedFood);

		// update it
		Carbohydrates carbs = new Carbohydrates(new Gram(26));
		carbs.setSugar(new Gram(16));
		carbs.setFiber(new Gram(.1));

		Fats fats = new Fats(new Gram(4.6));
		fats.setSaturated(new Gram(2));

		Minerals vms = new Minerals();
		vms.setSodium(new MilliGram(200));

		// make it a banana twinkie
		twinkie.getDescription().setProductName("Banana Twinkie");
		twinkie.setCalories(new Calorie(152));
		twinkie.setProtein(new Protein(new Gram(1.3)));
		twinkie.setCarbs(carbs);
		twinkie.setFats(fats);
		twinkie.setMinerals(vms);

		// update the chages
		dao.createOrUpdate(twinkie);

		// try pulling it out and verify changes
		fetchedFood = dao.readById(twinkie.getId());
		assertNotNull(fetchedFood);
		assertEquals(twinkie, fetchedFood);

		// delete the twinkie
		dao.delete(twinkie);

		// try and pull it out
		fetchedFood = dao.readById(twinkie.getId());
		assertNull(fetchedFood);
	}

	@Test
	public void test_saveOrUpdate_single() {
		Carbohydrates carbs = new Carbohydrates(new Gram(124));
		carbs.setFiber(new Gram(8));

		Fats fats = new Fats(new Gram(30));
		fats.setSaturated(new Gram(12));

		Minerals vms = new Minerals();
		vms.setSodium(new MilliGram(1240));
		vms.setSilicon(new MicroGram(25));

		Food pestoCavatappi = new Food();
		pestoCavatappi.setDescription(new FoodDescription("Pasta", "Noodles & Company", "Pesto Cavatappi"));
		pestoCavatappi.setServing(new Serving(new Ounce(25), "Small"));
		pestoCavatappi.setCalories(new Calorie(950));
		pestoCavatappi.setProtein(new Protein(new Gram(36)));
		pestoCavatappi.setCarbs(carbs);
		pestoCavatappi.setFats(fats);
		pestoCavatappi.setMinerals(vms);

		// save pesto cavatappi
		dao.createOrUpdate(pestoCavatappi);

		// verify the save went well
		Food fetchedFood = dao.readById(pestoCavatappi.getId());

		assertEquals(pestoCavatappi, fetchedFood);

		// nullify vita minerals
		pestoCavatappi.setMinerals(null);

		// modify calories
		pestoCavatappi.setCalories(new Calorie(940));

		// upate the changes
		dao.createOrUpdate(pestoCavatappi);

		// verify the update went well
		fetchedFood = dao.readById(pestoCavatappi.getId());
		assertEquals(pestoCavatappi, fetchedFood);
	}

	@Test
	public void test_fetchById() {
		// fetch a food known to be in the data store
		Food food = dao.readById(3L);
		assertNotNull(food);

		// fetch the same food by name instead of id
		Food nameFood = dao.readById(food.getId());
		assertEquals(food, nameFood);

		// fetch food for a non existent id
		food = dao.readById(-1L);
		assertNull(food);
	}

	@Test
	public void test_delete_single() {
		dao.createOrUpdate(twinkie);

		Food fetchedFood = dao.readById(twinkie.getId());
		assertNotNull(fetchedFood);

		dao.delete(twinkie);

		fetchedFood = dao.readById(twinkie.getId());
		assertNull(fetchedFood);
	}

	@Test
	public void test_delete_single_non_persisted() {
		try {
			dao.delete(new Food());
		}
		catch (Exception e) {
			fail("deleting an empty food shouldn't do anything");
		}
	}

	@Test
	public void test_delete_multi() {
		List<Food> twinkies = new ArrayList<Food>();
		twinkies.add(twinkie);
		twinkies.add(bananaTwinkie);

		dao.createOrUpdate(twinkies);

		assertNotNull(dao.readById(twinkie.getId()));
		assertNotNull(dao.readById(bananaTwinkie.getId()));

		dao.delete(twinkies);

		assertNull(dao.readById(twinkie.getId()));
		assertNull(dao.readById(bananaTwinkie.getId()));
	}

	@Test
	public void test_delete_multi_non_persisted() {
		List<Food> twinkies = new ArrayList<Food>();
		twinkies.add(twinkie);

		// save the list of twinkies
		dao.createOrUpdate(twinkies);

		// verify the save went well
		assertNotNull(dao.readById(twinkie.getId()));

		twinkies.add(bananaTwinkie);
		dao.delete(twinkies);

		assertNull(dao.readById(twinkie.getId()));
		assertNull(dao.readById(bananaTwinkie.getId()));
	}
}
